#include "ui_sprite.h"
#include "ui_widget.h"
#include "sprite_set.h"

#include "resman/resman.h"
#include "resman/resource_file.h"
#include "ui_manager.h"

#include "graphics/image.h"
#include "graphics/graphics.h"
#include "graphics/graphics_util.h"



#include "util/timer.h"				// Needs to come in early
#include "util/mmgr.h"				// Needs to come in early
#include "util/common_macros.h"		// macros
#include "util/log.h"				// logging


using namespace ui;
using namespace std;				// CHIT: for min and max


unsigned char UISprite::MASK_ALPHA_TOLERANCE = 10;
bool UISprite::NO_LOAD_IMAGE		= false;

UISprite::UISprite() :
	m_pImage(0),
	m_Additive(false),
	m_DrawCentered(true),
	m_pSpriteInstance(NULL),
	m_Timer(0),
	m_Rotation(0),
	m_Color(0xFFFFFFFF),
	m_UV1(0,0),
	m_UV2(1,1),
	m_QuadRotation(ROTATION_0),
	m_IsLocalized(false),
	m_Pause(false),
	m_Loop(true),
	m_LoopC(0),
	m_pMask(NULL),
	m_IsFlipped(false),
	m_FirstTimeLoaded(false)
{
}

UISprite::~UISprite()
{
	Invalidate();
}


void	UISprite::Invalidate()
{
	if (m_pSpriteInstance)
	{
		// this is not our sprite to deal with
		m_pSpriteInstance = NULL;
		m_pImage = NULL;
		m_pMask = NULL;  
	} else
	{
		UTIL_SAFE_DELETE(m_pImage);		
		UTIL_SAFE_DELETE(m_pMask);
	}
	
}

void	UISprite::FillBuffer(Graphics::Vertex2D* VertA)
{
	static math::Vec2	UV1, UV2;
	UV1 = m_UV1;
	UV2 = m_UV2;
	if (m_IsFlipped)
	{
		float t=UV1.x;	UV1.x = UV2.x; UV2.x = t;
	}
	GraphicsUtil::FillRotatedQuad(m_QuadRotation, m_Pos, m_Size, UV1, UV2, m_Color, VertA);
}


void	UISprite::FillClippedBuffer(Graphics::Vertex2D* VertA,  const math::Vec2& ClipMin, const math::Vec2& ClipMax )
{
	// clipping broken
	if (m_QuadRotation!=ROTATION_0) 
	{
		FillBuffer(VertA);
		return;
	}
	// work out the clipping coords
	math::Vec2	From	= m_Pos;
	math::Vec2	To		= m_Pos + m_Size;
	math::Vec2	Dest(To); 
	math::Vec2	ClipUV1(0,0);
	math::Vec2	ClipUV2(1,1);
	bool DisableSnap = false;

	if (From.x < ClipMin.x)
	{
		DisableSnap = GraphicsUtil::SnapToPixel;
		From.x = ClipMin.x;
		ClipUV1.x = (From.x-m_Pos.x) / m_Size.x;
	} 
	if (To.x > ClipMax.x)
	{
		To.x = ClipMax.x;
		ClipUV2.x = 1.0f - ((Dest.x-To.x) / m_Size.x);
	}

	if (From.y < ClipMin.y)
	{
		DisableSnap = GraphicsUtil::SnapToPixel;
		From.y = ClipMin.y;
		ClipUV1.y = (From.y-m_Pos.y) / m_Size.y;
	} 
	if (To.y > ClipMax.y)
	{
		To.y = ClipMax.y;
		ClipUV2.y = 1.0f - ((Dest.y-To.y) / m_Size.y);
	}

	math::Vec2	UV1(m_UV1);
	math::Vec2	UV2(m_UV2);
	UV1.x = UV1.x + ((UV2.x - UV1.x)*ClipUV1.x);
	UV1.y = UV1.y + ((UV2.y - UV1.y)*ClipUV1.y);
	UV2.x = UV1.x + ((UV2.x - UV1.x)*ClipUV2.x);
	UV2.y = UV1.y + ((UV2.y - UV1.y)*ClipUV2.y);

	if (m_IsFlipped)
	{
		float t=UV1.x;	UV1.x = UV2.x; UV2.x = t;
	}
	if (DisableSnap)
	{
		// if From is clipped, cannot have SnapToPixel otherwise it will keep rounding the From coord, resulting in jitter
		GraphicsUtil::SnapToPixel = false;
		GraphicsUtil::FillRotatedQuad(m_QuadRotation, From, To-From, UV1, UV2, m_Color, VertA);
		GraphicsUtil::SnapToPixel = true;
	}
	else
		GraphicsUtil::FillRotatedQuad(m_QuadRotation, From, To-From, UV1, UV2, m_Color, VertA);
}


void	UISprite::FillRotBuffer(Graphics::Vertex2D* VertA, float Rot)
{

	static math::Vec2	UV1, UV2;
	UV1 = m_UV1;
	UV2 = m_UV2;
	if (m_IsFlipped)
	{
		float t=UV1.x;	UV1.x = UV2.x; UV2.x = t;
	}

	GraphicsUtil::FillRotQuad(m_Pos, m_Size, Rot, UV1, UV2, m_Color, VertA);
}


void	UISprite::Setup(const math::Vec2& Pos, 
						const math::Vec2& Size, 
						float			Rotation,
						float			Scale ,
						COLOR			Color )
{
	m_Pos = Pos;
	m_Size = Size * Scale;
	m_Rotation = Rotation;
	m_Color = Color;
}	

bool	UISprite::SetTexture()
{
	Graphics* pG = GetGraphics();
	if (m_pSpriteInstance && !m_pSpriteInstance->pImage)
	{
		m_pSpriteInstance->Init();
	}

	pG->SetTexture(0, m_pImage);
		

	return true;
}

void	UISprite::FlushImage()
{
	if (m_pSpriteInstance)
	{
		m_pSpriteInstance->Flush();
	} else
	if (m_pImage)
	{
		res::ResMan::Instance().ReleaseResource(m_pImage->GetFile());
	}
}

bool	UISprite::Render()
{
	if (!m_pImage && !m_pSpriteInstance) return false;
	
	//----------------------
	if(m_pImage)
	{
		Surface* pSurf = m_pImage->GetSurface();
		if(pSurf)
		{
			res::ResourceFile::RES_STATUS Status = pSurf->GetState();
			if(Status == res::ResourceFile::RS_READY)
			{
				// ok
			}
			else
			{
				//something wrong
				//UIManager::Instance().AddToReCacheList(pSurf);
				return true;

			}
		}
	}
	//----------------------

#ifdef _DEBUG
	if( m_Size.x == 0 && m_Size.y == 0)
	{
		int debugtest = 0;
	}
#endif

	Graphics* pG = GetGraphics();

	Graphics::Vertex2D*		VertA = pG->GetTempVert();
	
	FillBuffer(VertA);

	SetTexture();

	if (m_Additive)
	{
		GetGraphics()->SetupAdditiveAlpha(true);
	}
	pG->DrawVertex2D(VertA, 6);

	if (m_Additive)		// reset
	{
		GetGraphics()->SetupAdditiveAlpha(false);
	}
	return true;
}

bool	UISprite::RenderRotated()
{
	if (!m_pImage && !m_pSpriteInstance) return false;

	if(m_pImage)
	{
		Surface* pSurf = m_pImage->GetSurface();
		if(pSurf)
		{
			res::ResourceFile::RES_STATUS Status = pSurf->GetState();
			if(Status == res::ResourceFile::RS_READY)
			{
				// ok
			}
			else
			{
				//something wrong
				//UIManager::Instance().AddToReCacheList(pSurf);
				return true;
				

			}
		}
	}

#ifdef _DEBUG
	if( m_Size.x == 0 && m_Size.y == 0)
	{
		int debugtest = 0;
	}
#endif


	SetTexture(); 

	Graphics* pG = GetGraphics();

	Graphics::Vertex2D*		VertA = pG->GetTempVert();
	
	GraphicsUtil::FillRotQuad(m_Pos, m_Size, m_Rotation, m_UV1, m_UV2, m_Color, VertA);

	

	if (m_Additive)
	{
		GetGraphics()->SetupAdditiveAlpha(true);
	}
	pG->DrawVertex2D(VertA, 6);

	if (m_Additive)		// reset
	{
		GetGraphics()->SetupAdditiveAlpha(false);
	}
	return true;
}


bool	UISprite::RenderClipped( const math::Vec2& ClipMin, const math::Vec2& ClipMax )
{
	if (!m_pImage && !m_pSpriteInstance) return false;
	if(m_pImage)
	{
		Surface* pSurf = m_pImage->GetSurface();
		if(pSurf)
		{
			res::ResourceFile::RES_STATUS Status = pSurf->GetState();
			if(Status == res::ResourceFile::RS_READY)
			{
				// ok
			}
			else
			{
				//something wrong
				//UIManager::Instance().AddToReCacheList(pSurf);
				return false;
				

			}
		}
	}

	float size = UTIL_HIGH(m_Size.x, m_Size.y);
	if ( (m_Pos.x < ClipMin.x && m_Pos.x + size < ClipMin.x) || 
		 (m_Pos.x > ClipMax.x ) || 
		 (m_Pos.y < ClipMin.y && m_Pos.y + size < ClipMin.y) || 
		 (m_Pos.y > ClipMax.y ))
	{
		return false;	// outside
	}

	Graphics* pG = GetGraphics();
	Graphics::Vertex2D*		VertA = pG->GetTempVert();

	FillClippedBuffer(VertA,  ClipMin, ClipMax );

	SetTexture();
	pG->DrawVertex2D(VertA, 6);
	return true;
}

void	UISprite::UpdateLocalization()
{
	if (m_IsLocalized)
	{
		SetImage(m_Filename);
	}
}


void	UISprite::SetImage(const std::string& ImageFile)
{
	if (IsValid()) Invalidate();
	if (NO_LOAD_IMAGE) return;	// some games won't run images

	if (ImageFile.empty())	
	{
		m_Filename.resize(0);
		m_pImage = NULL;
		m_pSpriteInstance = NULL;
		m_IsLocalized = false;
		return ;
	}

	std::string	Image = ImageFile;
/*
	if (ImageFile[0] == '#')	// this darn rare. Remove that find from the main loop
	{
		int	Locale = ImageFile.find("###");
		if (Locale >=0)
		{
			Image = ImageFile.substr(0, Locale) + 
					UIManager::Instance().GetLocalization() + 
					ImageFile.substr(Locale+3, ImageFile.size()-(Locale+3));

			m_IsLocalized = true;
		}
	}
*/	
	SpriteInstance*	pSI = SpriteSet::Instance().GetSprite(Image);
	if (pSI)
	{
		pSI->Init();		// force a load 
		m_pImage = NULL;	// we're using the sprite instance instead
		m_pSpriteInstance = pSI;
		m_Timer = 0.0f;
		m_LoopC = 0;
		m_pImage = m_pSpriteInstance->pImage;
	} else
	{
		m_pSpriteInstance = NULL;
		
		if (Image.size() > 4 && Image[Image.size()-4] == '.')
		{
			m_pImage = GetGraphics()->LoadImageFromFile(Image);
			if (!m_pImage)
			{
				_LOG(MSG_ERROR, "Unable to load image " << Image << " into the UI sprite");
				return ;
			}
		}
	}
	
	m_Filename = ImageFile;
	return;
}



void UISprite::SetImage(const char* ImageData, int DataSize)
{
	if (IsValid()) Invalidate();

	m_pSpriteInstance = NULL;

	if (ImageData == NULL || DataSize < 1)
	{
		_LOG(MSG_ERROR, "Unable to load image from memory: provided null data");
		return;
	}

	m_pImage = GetGraphics()->LoadImageFromMemory((unsigned char*)ImageData, DataSize); 

	if (m_pImage == NULL)
		_LOG(MSG_ERROR, "Unable to load image from memory: provided invalid data");

}





bool	UISprite::Create(const std::string& ImageFile, 
						const math::Vec2& UV1, 
						const math::Vec2& UV2,
						bool  Additive)
{

	if (ImageFile.empty())
	{
		Invalidate();
		return false;
	}

	m_UV1 = UV1;
	m_UV2 = UV2;
	m_Filename = ImageFile;
	m_FirstTimeLoaded = false;	// reset it.

	/*
	// everything is now forced to lazy loading
	Tick(0);
	SetImage(ImageFile);
	GetScreenSize(m_Size);
	m_SizeOriginal = m_Size;
	*/
	
	m_Additive = Additive;
	

	//if (m_pMask) CreateMask();
	return true;
}

bool	UISprite::CreateMask()
{
	
	if (m_pSpriteInstance && m_pSpriteInstance->pImage)
	{
		if(m_pSpriteInstance->HasMask())
			m_pMask = m_pSpriteInstance->GetMask();
		else 
			m_pMask = m_pSpriteInstance->GenerateMask();		 
	}
	else if (m_pImage)
	{
		UTIL_SAFE_DELETE(m_pMask);
		if (Surface* pSurf = m_pImage->GetSurface())
			m_pMask = pSurf->GenerateMask();
	}
	return (NULL != m_pMask);
}

float	UISprite::GetTotalAnimTime() const
{
	return m_pSpriteInstance ? m_pSpriteInstance->GetAnimTime() : 1.0f;
}


void	UISprite::SetAnimTime(float time) 
{
	m_Timer = time;
}

void	UISprite::SetAnimTimeNormalized(float time) 
{
	const float AnimTime = GetTotalAnimTime();
	m_Timer = time * AnimTime;
}

float	UISprite::GetAnimTime() const 
{
	return m_Timer;
}
float	UISprite::GetAnimTimeNormalized() const
{
	return m_Timer/GetTotalAnimTime();
}

int		UISprite::GetFrame() const 
{
	if (m_pSpriteInstance)
	{
		return int(m_Timer / m_pSpriteInstance->FrameTime) % (m_pSpriteInstance->SpriteC); 
	}
	return 0;
}


bool UISprite::Tick(float dt)
{
	if (m_Pause)
		dt = 0.0f;

	RenderCheck();

	if (m_pSpriteInstance)
	{
		m_pSpriteInstance->GetUV(m_Timer, m_UV1, m_UV2);
		if(dt > 0)
		{
			if (!m_pSpriteInstance->UpdateTime(m_LoopC, m_Timer, dt, m_Loop))
			{
				return false;
			}
		}
		return true;
	} 
	return false;
}

void	UISprite::GetScreenSize(math::Vec2& Size, float Scale) const
{
	if (m_pSpriteInstance)
	{
		m_pSpriteInstance->GetFrameSize(Size);
	} else
	if (m_pImage)
	{
		Size.x = ((m_UV2.x - m_UV1.x) * Scale * m_pImage->GetWidth()) / (float)GraphicsUtil::WIDTH;
		Size.y = ((m_UV2.x - m_UV1.x) * Scale * m_pImage->GetHeight()) / (float)GraphicsUtil::HEIGHT;
	}
}

bool	UISprite::IsInside(const math::Vec2& Pos) const
{
	// update to handle rotated and centeredimages?
	return	UTIL_RANGE(Pos.x, m_Pos.x, m_Pos.x+m_Size.x) && 
			UTIL_RANGE(Pos.y, m_Pos.y, m_Pos.y+m_Size.y);
}

bool	UISprite::IsInsideMask(const math::Vec2& Pos) const
{
	if (!m_pMask) return false;
	//if (!IsInside(Pos)) return false;

	// TODO: factor in rotation and scaling?
	math::Vec2 FSize, InvScale;
	GetScreenSize(FSize);
	InvScale.x = FSize.x / m_Size.x;
	InvScale.y = FSize.y / m_Size.y;

	// find what the pixel position is?
	float x = InvScale.x * (Pos.x - m_Pos.x) * (float)GraphicsUtil::WIDTH;
	float y = InvScale.y * (Pos.y - m_Pos.y) * (float)GraphicsUtil::HEIGHT;

	// find what is the current frame?
	if (m_pSpriteInstance && (m_pSpriteInstance->SpriteC > 1))
	{
		math::Vec2 UV1, UV2;
		m_pSpriteInstance->GetUV(m_Timer, UV1, UV2);

		int dx1 = (int)(UV1.x * (float)m_pMask->Width);
		//int dx2 = (int)(UV2.x * (float)m_pMask->Width);
		int dy1 = (int)(UV1.y * (float)m_pMask->Height);
		//int dy2 = (int)(UV2.y * (float)m_pMask->Height);

		int X = (int)x + 2 + dx1;
		int Y = (int)y + 2 + dy1;

		UTIL_CLAMP(X, 0, m_pMask->Width-1);
		UTIL_CLAMP(Y, 0, m_pMask->Height-1);

		return IsInsideMask(X, Y);
	}
	else
	{
		int	X = (int)x + 2;
		int	Y = (int)y + 2;
		
		UTIL_CLAMP(X, 0, m_pMask->Width-1);
		UTIL_CLAMP(Y, 0, m_pMask->Height-1);

		return IsInsideMask(X, Y);
	}
}

bool UISprite::IsInsideMask(UISprite* pSprite) const
{
	if (!m_pMask) return false;
	if (!pSprite) return false;
	if (!pSprite->m_pMask) return false;

	// check for sprite overlap
	if ((m_Pos.x + m_Size.x) < pSprite->m_Pos.x) return false;
	if ((m_Pos.y + m_Size.y) < pSprite->m_Pos.y) return false;
	if ((pSprite->m_Pos.x + pSprite->m_Size.x) < m_Pos.x) return false;
	if ((pSprite->m_Pos.y + pSprite->m_Size.y) < m_Pos.y) return false;

	// find pixel positions for both sprites
	math::Vec2 Pos;
	math::Vec2 UV1, UV2;
	math::Vec2 FSize, InvScale;
	float x, y;
	int fromx1, fromx2, tox1, tox2;
	int fromy1, fromy2, toy1, toy2;

	Pos.x = min(m_Pos.x, pSprite->m_Pos.x);
	Pos.y = min(m_Pos.y, pSprite->m_Pos.y);

	// self
		// TODO: factor in rotation and scaling?
		GetScreenSize(FSize);
		InvScale.x = FSize.x / m_Size.x;
		InvScale.y = FSize.y / m_Size.y;
		x = InvScale.x * (Pos.x - m_Pos.x) * (float)GraphicsUtil::WIDTH;
		y = InvScale.y * (Pos.y - m_Pos.y) * (float)GraphicsUtil::HEIGHT;
		if (m_pSpriteInstance && (m_pSpriteInstance->SpriteC > 1))
		{
			m_pSpriteInstance->GetUV(m_Timer, UV1, UV2);
			fromx1 = (int)x + 2 + (int)(UV1.x * (float)m_pMask->Width);
			tox1 = (int)x + 2 + (int)(UV2.x * (float)m_pMask->Width);
			fromy1 = (int)y + 2 + (int)(UV1.y * (float)m_pMask->Height);
			toy1 = (int)y + 2 + (int)(UV2.y * (float)m_pMask->Height);
		}
		else
		{
			fromx1 = (int)x + 2;
			tox1 = m_pMask->Width - 1;
			fromy1 = (int)y + 2;
			toy1 = m_pMask->Height - 1;
		}

	// target
		// TODO: factor in rotation and scaling?
		pSprite->GetScreenSize(FSize);
		InvScale.x = FSize.x / pSprite->m_Size.x;
		InvScale.y = FSize.y / pSprite->m_Size.y;
		x = InvScale.x * (Pos.x - pSprite->m_Pos.x) * (float)GraphicsUtil::WIDTH;
		y = InvScale.y * (Pos.y - pSprite->m_Pos.y) * (float)GraphicsUtil::HEIGHT;
		if (pSprite->m_pSpriteInstance && (pSprite->m_pSpriteInstance->SpriteC > 1))
		{
			pSprite->m_pSpriteInstance->GetUV(pSprite->m_Timer, UV1, UV2);
			fromx2 = (int)x + 2 + (int)(UV1.x * (float)pSprite->m_pMask->Width);
			tox2 = (int)x + 2 + (int)(UV2.x * (float)pSprite->m_pMask->Width);
			fromy2 = (int)y + 2 + (int)(UV1.y * (float)pSprite->m_pMask->Height);
			toy2 = (int)y + 2 + (int)(UV2.y * (float)pSprite->m_pMask->Height);
		}
		else
		{
			fromx2 = (int)x + 2;
			tox2 = pSprite->m_pMask->Width - 1;
			fromy2 = (int)y + 2;
			toy2 = pSprite->m_pMask->Height - 1;
		}

	// find overlapping region
	int fx = max(fromx1, fromx2);
	int tx = min(tox1, tox2);
	int fy = max(fromy1, fromy2);
	int ty = min(toy1, toy2);

	if ((tx < fx) || (ty < tx)) return false;

	UTIL_CLAMP(fx, 0, min(m_pMask->Width-1, pSprite->m_pMask->Width-1));
	UTIL_CLAMP(fy, 0, min(m_pMask->Height-1, pSprite->m_pMask->Height-1));
	UTIL_CLAMP(tx, 0, min(m_pMask->Width-1, pSprite->m_pMask->Width-1));
	UTIL_CLAMP(ty, 0, min(m_pMask->Height-1, pSprite->m_pMask->Height-1));

	// test overlapping regions
	for (int dx = fx; dx <= tx; dx++)
	for (int dy = fy; dy <= ty; dy++)
	{
		if (IsInsideMask(dx, dy) && pSprite->IsInsideMask(dx, dy))
			return true;
	}

	return false;
}

bool UISprite::IsInsideMask(int x, int y) const
{
	assert(m_pMask);
	assert((x>=0) && (x<m_pMask->Width));
	assert((y>=0) && (y<m_pMask->Height));

	int Index = x + (y * m_pMask->Width);
	unsigned char	Alpha = m_pMask->AlphaVal[Index];

	//unsigned char test = m_pMask->AlphaVal[0];
	return Alpha > MASK_ALPHA_TOLERANCE;
}

///////////////////////////////////////////////
math::Vec2	UISprite::GetImageSize() const
{
	math::Vec2 uv1 = m_UV1, uv2 = m_UV2;
	math::Vec2 imageSize(0,0);
	Image* pImage = m_pImage;
	if (m_pSpriteInstance) {
		pImage = m_pSpriteInstance->pImage;
	}
	if (pImage) {
		imageSize.x = (uv2.x-uv1.x) * (float)pImage->GetWidth() / (float)GraphicsUtil::WIDTH;
		if (imageSize.x < 0) imageSize.x = -imageSize.x;
		imageSize.y = (uv2.y-uv1.y) * (float)pImage->GetHeight() / (float)GraphicsUtil::HEIGHT;
		if (imageSize.y < 0) imageSize.y = -imageSize.y;
	}
	return imageSize;
}

void	UISprite::RenderCheck()
{
	
	bool ToLoad = false;

	if( !m_FirstTimeLoaded)
	{
		m_FirstTimeLoaded = true;
		ToLoad = true;
	}

	if(m_pImage)
	{
		Surface* pSurf = m_pImage->GetSurface();
		if(pSurf)
		{
			res::ResourceFile::RES_STATUS Status = pSurf->GetState();
			if(Status == res::ResourceFile::RS_READY)
			{
				// ok
			}
			else
			{
				ToLoad = true;
			}
		}
	}

	if (m_pSpriteInstance)
	{
		if(m_pSpriteInstance->pImage)
		{

			Surface* pSurf = m_pSpriteInstance->pImage->GetSurface();
			if(pSurf)
			{
				res::ResourceFile::RES_STATUS Status = pSurf->GetState();
				if(Status == res::ResourceFile::RS_READY)
				{
					// ok
				}
				else
				{
					ToLoad = true;
				}
			}
		}
	}

	if(ToLoad)
	{
		if(!m_Filename.empty())
		{
			SetImage(m_Filename);
			GetScreenSize(m_Size);
			m_SizeOriginal = m_Size;
			if (m_pMask) 
				CreateMask();

		
		}
	}
}
